package ch.janotta.slawo.da.algirithm.lifecycle;

import java.util.ArrayList;
import java.util.List;

import ch.janotta.slawo.da.Ability;
import ch.janotta.slawo.da.Background;
import ch.janotta.slawo.da.CharacterClass;
import ch.janotta.slawo.da.Entity;
import ch.janotta.slawo.da.Focus;
import ch.janotta.slawo.da.Gender;
import ch.janotta.slawo.da.People;
import ch.janotta.slawo.da.Talent;
import ch.janotta.slawo.da.modifier.AbilityIncreaseModifier;
import ch.janotta.slawo.da.modifier.AdditionalFocusModifier;
import ch.janotta.slawo.da.modifier.Modifier;
import ch.janotta.slawo.da.tables.BackgroundStartingModifiersTable;
import ch.janotta.slawo.da.tables.BackgroundModifiersTableFactory;
import ch.janotta.slawo.da.tables.CharacterClassModifiersTable;
import ch.janotta.slawo.da.tables.CharacterClassModifiersTableFactory;
import ch.janotta.slawo.utilities.Dice;

public abstract class AbstractEntityLifecycleHandlingAlgorithm implements EntityLifecycleHandlingAlgorithm {
  @Override
  public Entity createNewInstance() {
    Entity entity = new Entity();
    entity.setLevel(1);
    generateInitialAbilities(entity);
    generateGender(entity);
    generateBackground(entity);
    generatePeople(entity);
    generateCharacterClass(entity);
    adjustDueToStartingBackground(entity);
    adjustDueToCharacterClass(entity);
    return entity;
  }

  @Override
  public void advanceLevel(Entity entity) {
    increaseLevel(entity);
    increaseHealth(entity);
    increaseAbility(entity);
    addAbilityFocus(entity);
    performAdditionalLevelUpOperations(entity);
  }

  protected abstract void generateInitialAbilities(Entity entity);
  protected abstract void generateBackground(Entity entity);
  protected abstract void generateCharacterClass(Entity entity);
  protected abstract void performAdditionalLevelUpOperations(Entity entity);

  private void generateGender(Entity entity) {
    entity.setGender(Gender.getRandomInstance());
  }

  private void generatePeople(Entity entity) {
    People[] possiblePeople = entity.getBackground().getPossiblePeople();
    int lastPossiblePeople = possiblePeople.length;
    int randomPeopleIndex = Dice.getRandomNumber(lastPossiblePeople) - 1;
    entity.setPeople(possiblePeople[randomPeopleIndex]);
  }

  private void adjustDueToStartingBackground(Entity entity) {
    Background background = entity.getBackground();
    People people = entity.getPeople();
    BackgroundStartingModifiersTable modifiersTable =
        BackgroundModifiersTableFactory.getBackgroundModifiersTable(background, people);
    Modifier[] allBackgroundModifiers = modifiersTable.getAllStartingModifiers();
    for (Modifier modifier : allBackgroundModifiers) {
      modifier.apply(entity);
    }
  }

  private void adjustDueToCharacterClass(Entity entity) {
    CharacterClass characterClass = entity.getCharacterClass();
    CharacterClassModifiersTable modificationTable =
        CharacterClassModifiersTableFactory.getCharacterClassModifiersTable(characterClass);
    entity.setPrimaryAbilities(modificationTable.getPrimaryAbilities());
    entity.setInitialHealth(modificationTable.getStartingHealth(entity));
    entity.setInitialMana(modificationTable.getStartingMana(entity));
    entity.addTalent(modificationTable.getStartingTalent());
    for (Talent talent : modificationTable.getStartingWeaponGroups()) {
      entity.addTalent(talent);
    }
    Modifier[] allLevel1Modifiers = modificationTable.getLevelUpModifiers(1);
    for (Modifier modifier : allLevel1Modifiers) {
      modifier.apply(entity);
    }
  }

  private void increaseLevel(Entity entity) {
    entity.setLevel(entity.getLevel() + 1);
  }

  private void increaseHealth(Entity entity) {
    entity.setCurrentHealth(entity.getCurrentHealth() + entity.getConstitution() + Dice.getD6());
  }

  private void increaseAbility(Entity entity) {
    if (entity.getLevel() % 2 == 0) {
      increasePrimaryAbility(entity);
    } else {
      increaseSecondaryAbility(entity);
    }
  }

  private void increasePrimaryAbility(Entity entity) {
    Ability[] primaryAbilities = entity.getCharacterClass().getPrimaryAbilities();
    increaseOneAbility(entity, primaryAbilities);
  }

  private void increaseSecondaryAbility(Entity entity) {
    increaseOneAbility(entity, getSecondaryAbilities(entity));
  }

  private void increaseOneAbility(Entity entity, Ability[] possibleAbilities) {
    int abilityToIncreaseIndex = Dice.getRandomNumber(possibleAbilities.length) - 1;
    Ability abilityToIncrease = possibleAbilities[abilityToIncreaseIndex];
    Modifier abilityModifier = new AbilityIncreaseModifier(abilityToIncrease, 1);
    abilityModifier.apply(entity);
  }

  private void addAbilityFocus(Entity entity) {
    if (entity.getLevel() % 2 == 0) {
      addPrimaryAbilityFocus(entity);
    } else {
      addSecondaryAbilityFocus(entity);
    }
  }

  private void addPrimaryAbilityFocus(Entity entity) {
    ArrayList<Focus> primaryFocusses = new ArrayList<>();
    Ability[] primaryAbilities = entity.getCharacterClass().getPrimaryAbilities();
    for (Ability ability : primaryAbilities) {
      primaryFocusses.addAll(Focus.getAbilityFocusses(ability));
    }
    addOneFocus(entity, primaryFocusses);
  }

  private void addSecondaryAbilityFocus(Entity entity) {
    ArrayList<Focus> secondaryFocusses = new ArrayList<>();
    Ability[] secondaryAbilities = getSecondaryAbilities(entity);
    for (Ability ability : secondaryAbilities) {
      secondaryFocusses.addAll(Focus.getAbilityFocusses(ability));
    }
    addOneFocus(entity, secondaryFocusses);
  }

  private void addOneFocus(Entity entity, List<Focus> focusses) {
    int numberOfPossibleFocusses = focusses.size();
    int randomFocusIndex = Dice.getRandomNumber(numberOfPossibleFocusses) - 1;
    Focus randomFocus = focusses.get(randomFocusIndex);
    while (entity.hasFocus(randomFocus)) {
      randomFocusIndex = Dice.getRandomNumber(numberOfPossibleFocusses);
      randomFocus = focusses.get(randomFocusIndex);
    }
    Modifier additionalFocus = new AdditionalFocusModifier(randomFocus);
    additionalFocus.apply(entity);
  }

  private Ability[] getSecondaryAbilities(Entity entity) {
    ArrayList<Ability> secondaryAbilities = new ArrayList<>();
    for (Ability ability: Ability.values()) {
      if (!entity.isPrimaryAbility(ability)) {
        secondaryAbilities.add(ability);
      }
    }
    return secondaryAbilities.toArray(new Ability[0]);
  }
}
